﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration;
using System.Data.Entity.ModelConfiguration.Configuration;

namespace System.Data.Entity
{
    public abstract class GenericModelDescription<TConfiguration, TClass>
        where TConfiguration : StructuralTypeConfiguration<TClass>
        where TClass : class
    {
        protected DbModelBuilder Builder { get; private set; }

        internal void SetBuilder(DbModelBuilder modelBuilder)
        {
            if (modelBuilder == null) throw new ArgumentNullException("modelBuilder");
            Builder = modelBuilder;
        }

        public abstract void DescribeInto(TConfiguration model);
    }

    public abstract class EntityModelDescription<TClass> : GenericModelDescription<EntityTypeConfiguration<TClass>, TClass>
        where TClass : class
    {        
    }


    public abstract class ComplexTypeModelDescription<TClass> : GenericModelDescription<ComplexTypeConfiguration<TClass>,TClass>
        where TClass : class
    {        
    }

    internal class DefaultEntityModelDescription<TClass> : EntityModelDescription<TClass> where TClass : class
    {
        public override void DescribeInto(EntityTypeConfiguration<TClass> model)
        {
            // Simply do nothing
        }
    }

    public static class DbModelBuilderExtensions
    {
        public static EntityModelDescriptionManager<TEntity> AddModelDescriptionFor<TEntity>(this DbModelBuilder modelBuilder)
            where TEntity : class
        {
            return new EntityModelDescriptionManager<TEntity>(modelBuilder);
        }

        public static ComplexTypeModelDescriptionManager<TComplexType> AddComplexTypeDescriptionFor<TComplexType>(this DbModelBuilder modelBuilder)
            where TComplexType : class
        {
            return new ComplexTypeModelDescriptionManager<TComplexType>(modelBuilder);
        }
    }

    public class EntityModelDescriptionManager<TEntity> where TEntity : class
    {
        private readonly DbModelBuilder modelBuilder;

        internal EntityModelDescriptionManager(DbModelBuilder modelBuilder)
        {
            if (modelBuilder == null) throw new ArgumentNullException("modelBuilder");
            this.modelBuilder = modelBuilder;
        }

        public EntityTypeConfiguration<TEntity> With<TModelDescriber>()
            where TModelDescriber : EntityModelDescription<TEntity>, new()
        {
            return WithDescription(new TModelDescriber());
        }

        public EntityTypeConfiguration<TEntity> WithDefault()
        {
            return WithDescription(new DefaultEntityModelDescription<TEntity>());
        }

        private EntityTypeConfiguration<TEntity> WithDescription(EntityModelDescription<TEntity> description)
        {
            if (description == null) throw new ArgumentNullException("describer");            
            description.SetBuilder(modelBuilder);
            return modelBuilder.Entity<TEntity>().Describe(config => description.DescribeInto(config));
        }
    }

    public class ComplexTypeModelDescriptionManager<TComplexType> where TComplexType : class
    {
        private readonly DbModelBuilder modelBuilder;

        internal ComplexTypeModelDescriptionManager(DbModelBuilder modelBuilder)
        {
            if (modelBuilder == null) throw new ArgumentNullException("modelBuilder");
            this.modelBuilder = modelBuilder;
        }

        public ComplexTypeConfiguration<TComplexType> With<TComplexTypeDescription>()
            where TComplexTypeDescription : ComplexTypeModelDescription<TComplexType>, new()
        {
            return WithDescription(new TComplexTypeDescription());
        }

        private ComplexTypeConfiguration<TComplexType> WithDescription(ComplexTypeModelDescription<TComplexType> description)
        {
            if (description == null) throw new ArgumentNullException("description");
            return modelBuilder.ComplexType<TComplexType>().Describe(config => description.DescribeInto(config));
        }
    }
}
